/*
 * serial-mhn.c: PXA250/210 serial port driver
 *
 * Copyright (C) 2002  Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
 * Copyright (C) 2002  Jeff Sutherland <jeffs@accelent.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <arch.h>
#include <errno.h>
#include <serial.h>
#include <types.h>

#if defined USE_SERIAL1

# define SerialDATA  FFRBR
# define SerialFIFO  FFFCR
# define SerialLCR   FFLCR
# define SerialMCR   FFMCR
# define SerialLSR   FFLSR
# define SerialMSR   FFMSR
# define SerialIER   FFIER

#else /* defined USE_SERIAL1 */

#if defined USE_SERIAL2

# define SerialDATA  BTRBR
# define SerialFIFO  BTFCR
# define SerialLCR   BTLCR
# define SerialMCR   BTMCR
# define SerialLSR   BTLSR
# define SerialIER   BTIER
# define SerialMSR   BTMSR

#else /* defined USE_SERIAL2 */

#if defined USE_SERIAL3

/* using an Xscale CPU and not having any serial port defined is an
 * error, but because we're using the driver in the library we can't
 * bail out over here
 */

# define SerialDATA  STRBR
# define SerialFIFO  STFCR
# define SerialLCR   STLCR
# define SerialMCR   STMCR
# define SerialLSR   STLSR
# define SerialIER   STIER
# define SerialMSR   STMSR

#else
#error "No serial port defined USE_SERIAL1 .. USE_SERIAL3"

#endif /* defined USE_SERIAL3 */

#endif /* defined USE_SERIAL2 */

#endif /* defined USE_SERIAL1 */

/* flush serial input queue. returns 0 on success or negative error
 * number otherwise
 */
static int mhn_serial_flush_input(void)
{
	volatile u32 tmp;

	/* keep on reading as long as the receiver is not empty */
	while(SerialLSR & LSR_DR) {
		if(SerialLSR & (LSR_FE | LSR_PE | LSR_OE))
			return -ESERIAL;

		tmp = SerialDATA;
	}

	return 0;
}

/* flush output queue. returns 0 on success or negative error number
 * otherwise.  This only waits for space in the tx fifo, NOT until the fifo
 * has completely emptied.
 */
static int mhn_serial_flush_output(void)
{
	/* wait until the transmitter is no longer busy */
	while(SerialLSR & ~LSR_TDRQ) ;

	return 0;
}

/* initialise serial port at the request baudrate. returns 0 on
 * success, or a negative error number otherwise
 */
 
int mhn_serial_init(serial_baud_t baud)
{
	u32 divisor;

	*(unsigned int *)GPIO109_MFPR = 0xC041; /* STTXD */
	*(unsigned int *)GPIO110_MFPR = 0x4041; /* STRXD */

	/* enab le clock */
	CKENA |= CKENA_23_STUART;
 
	switch(baud) {
	case baud_1200:
		divisor = 768;
		break;
 
	case baud_9600:
		divisor = 96;
		break;

	case baud_19200:
		divisor = 48;
		break;

	case baud_38400:
		divisor = 24;
		break;

	case baud_57600:
		divisor = 16;
		break;

	case baud_115200:
		divisor = 8;
		break;

	default:
		return -ERANGE;
	}

	/* switch receiver and transmitter off */
	SerialLCR = 0;
	SerialIER = 0;
	SerialFIFO = 0;
	
	/* Gain access to divisor latch */
	SerialLCR = LCR_WLS0 | LCR_WLS1 | LCR_DLAB;

	/* Load baud rate divisor in two steps, lsb, then msb of value */

	SerialDATA = divisor & 0xff;
	SerialIER = (divisor >> 8) & 0xff;
	

	/* set the port to sensible defaults (no break, no interrupts,
	 * no parity, 8 databits, 1 stopbit, transmitter and receiver
	 * enabled), reset dlab bit:
	 */
	SerialLCR = LCR_WLS1 | LCR_WLS0;

	/* turn the receiver and transmitter back on */
	SerialIER = IER_UUE;

	return 0;
}

/* check if there is a character available to read. returns 1 if there
 * is a character available, 0 if not, and negative error number on
 * failure */
static int mhn_serial_poll(void)
{
	/* check for errors */
	if(SerialLSR & (LSR_FE | LSR_PE | LSR_OE))
		return -ESERIAL;

	if(SerialLSR & LSR_DR)
		return 1;
	else
		return 0;
}

/* read one character from the serial port. return character (between
 * 0 and 255) on success, or negative error number on failure. this
 * function is blocking */
static int mhn_serial_read(void)
{
	int rv;

	for(;;) {
		rv = mhn_serial_poll();

		if(rv < 0)
			return rv;

		if(rv > 0)
			return SerialDATA & 0xff;
	}
}

void SerialOutputString(const char *s);
int mhn_serial_read_no_block(int *c)
{
    int rv;
    rv = mhn_serial_poll();
    if(rv <= 0)
        return rv;

    if(rv > 0)
        *c = SerialDATA & 0xff;
    return 1;
}

/* write character to serial port. return 0 on success, or negative
 * error number on failure. this function is blocking
 */
int mhn_serial_write(int c)
{
	/* wait for room in the transmit FIFO */
	while((SerialLSR & LSR_TDRQ) == 0) {
	}

	SerialDATA = c & 0xff;

	return 0;
}

/* export serial driver */
serial_driver_t mhn_serial_driver = {
	init:		mhn_serial_init,
	read:		mhn_serial_read,
	write:		mhn_serial_write,
	poll:		mhn_serial_poll,
	flush_input:	mhn_serial_flush_input,
	flush_output:	mhn_serial_flush_output
};

